#include "ET++.ph"
#include "Printer.h"

#include "Class.h"
#include "Picture.h"
#include "Data.h"
#include "String.h"
#include "MemBuf.h"
#include "Bitmap.h"
#include "DevBitmap.h"
#include "PrintPort.h"
#include "Math.h"
#include "PrintManager.h"

static struct {
	int id;
	GrFont fid;
} fmap[] = {
	{ 0,  eFontChicago },
	{ 20, eFontTimes },
	{ 21, eFontHelvetica },
	{ 22, eFontCourier },
	{ 23, eFontSymbol },
	{ 15011, eFontAvantgarde },
	{ -1, (GrFont)0 }
};

class MacPictPort: public PrintPort {
protected:
	OStream *os;
	Font *lastfont, *bfont;
	int lastpsz, bcnt, pagen;
	Point lastdia;
	byte *bbuf;
	MemBuf *mb;
	bool havepattern;
	char *name;

	void PutRect(const Rectangle &r);
	void PutPoint(const Point &p);
	void PutSize(int s);
	void PutCorner(const Point &dia);
	void PutFont(Font *fp);

public:
	MetaDef(MacPictPort);
	MacPictPort(const char *name);
	~MacPictPort();

	void DevOpenPage(int n);
	void DevClosePage();
	void DevClip(const Rectangle&);

	void DevShowChar(Font *fdp, const Point&, Rune c, const Rectangle&);
	void DevFillRect(const Rectangle&);
	void DevShowBitmap(const Rectangle&, Bitmap*);
	void DevSetPattern(Bitmap*);
	void DevSetColor(RGBColor *cp);
	void DevSetOther(int);
	void DevShowTextBatch(const Rectangle&, const Point&);

	void DevStrokeLine2(int, const Rectangle&, GrLineCap, const Point&, const Point&);
	void DevStrokeRect2(int, const Rectangle&);
	void DevStrokeOval2(int, const Rectangle&);
	void DevFillOval2(const Rectangle&);
	void DevStrokeRRect2(int, const Rectangle&, const Point&);
	void DevFillRRect2(const Rectangle&, const Point&);
	void DevStrokeWedge2(int, GrLineCap, const Rectangle&, int, int);
	void DevFillWedge2(const Rectangle&, int, int);
	void DevStrokePolygon2(const Rectangle&, Point*, int, GrPolyType t, int, GrLineCap);
	void DevFillPolygon2(const Rectangle&, Point*, int, GrPolyType t);
};

NewMetaImpl0(MacPictPort, PrintPort);

//---- MacPictPort ----------------------------------------------------------------

MacPictPort::MacPictPort(const char *nm) : PrintPort()
{
	hascolor= TRUE;
	bbuf= new byte[200];
	havepattern= FALSE;
	name= strsave(nm);
}

MacPictPort::~MacPictPort()
{
	ClosePage();
	SafeDelete(bbuf);
	SafeDelete(name);
}

void MacPictPort::DevOpenPage(int pn)
{
	mb= new MemBuf;
	os= new OStream(mb);
	pagen= pn;
}

void MacPictPort::DevClosePage()
{
	int i;

	if (os) {
		os->PutBigEndian(2, 0x00ff);
		os->flush();

		SafeDelete(os);
		os= new OStream(form("%s.%d", name, pagen));
		
		for (i= 0; i < 512; i++)
			os->put((char)0);

		os->PutBigEndian(2, mb->ContentsSize()+50);
		PutRect(pagebbox);

		os->PutBigEndian(2, 0x0011);
		os->PutBigEndian(2, 0x02ff);
		os->PutBigEndian(2, 0x0c00);
		
		os->PutBigEndian(2, -2);            // PICT2 version -2
		os->PutBigEndian(2, -1);            // reserved
		os->PutBigEndian(4, 0x00480000);    // native hor res
		os->PutBigEndian(4, 0x00480000);    // native vert res
		
		PutRect(pagebbox);                 // native src rect
		os->PutBigEndian(4, -1);            // reserved
		
		DevClip(pagebbox);
		
		mb->SwitchToRead();
		IStream is(mb);
		
		byte buf[4096];
		int l;
		
		do {
			l= is.read(buf, 4096);
			os->write(buf, l);
		} while (l == 4096);
		
		os->flush();

		SafeDelete(os);
	}
}

void MacPictPort::DevSetPattern(Bitmap *bm)
{
	DevBitmap *dbm= bm->GetDevBitmap();
	byte row[16];
	int y;
	BitmapInfo bi(bm->Size(), dbm->Depth(), 1);

	os->PutBigEndian(2, 0x0009);
	for (y= 0; y < 8; y++) {
		dbm->GetRow(&bi, y, row);
		os->PutBigEndian(1, row[0]);
	}
	os->PutBigEndian(2, 0x000A);
	for (y= 0; y < 8; y++) {
		dbm->GetRow(&bi, y, row);
		os->PutBigEndian(1, row[0]);
	}

	os->PutBigEndian(2, 0x001a);
	os->PutBigEndian(2, 0);
	os->PutBigEndian(2, 0);
	os->PutBigEndian(2, 0);

	havepattern= TRUE;
}

void MacPictPort::DevSetColor(RGBColor *cp)
{
	if (havepattern) {
		os->PutBigEndian(2, 0x0009);
		os->PutBigEndian(4, 0xffffffff);
		os->PutBigEndian(4, 0xffffffff);
		os->PutBigEndian(2, 0x000A);
		os->PutBigEndian(4, 0xffffffff);
		os->PutBigEndian(4, 0xffffffff);
		havepattern= FALSE;
	}

	os->PutBigEndian(2, 0x001a);
	os->PutBigEndian(2, cp->GetRed() * 257);
	os->PutBigEndian(2, cp->GetGreen() * 257);
	os->PutBigEndian(2, cp->GetBlue() * 257);
}

void MacPictPort::DevSetOther(int id)
{
	if (havepattern) {
		os->PutBigEndian(2, 0x0009);
		os->PutBigEndian(4, 0xffffffff);
		os->PutBigEndian(4, 0xffffffff);
		os->PutBigEndian(2, 0x000A);
		os->PutBigEndian(4, 0xffffffff);
		os->PutBigEndian(4, 0xffffffff);
		havepattern= FALSE;
	}

	os->PutBigEndian(2, 0x001a);
	switch (id) {
	case 1:     // white
		os->PutBigEndian(2, 0xffff);
		os->PutBigEndian(2, 0xffff);
		os->PutBigEndian(2, 0xffff);
		break;
	case 2:     // black
		os->PutBigEndian(2, 0);
		os->PutBigEndian(2, 0);
		os->PutBigEndian(2, 0);
		break;
	}
}

void MacPictPort::PutPoint(const Point &p)
{
	os->PutBigEndian(2, p.y);
	os->PutBigEndian(2, p.x);
}

void MacPictPort::PutRect(const Rectangle &r)
{
	PutPoint(r.origin);
	PutPoint(r.origin+r.extent);
}

void MacPictPort::PutCorner(const Point &dia)
{
	if (dia != lastdia) {
		os->PutBigEndian(2, 0x000b);
		PutPoint(dia);
		lastdia= dia;
	}
}

void MacPictPort::PutFont(Font *fp)
{
	if (fp != lastfont) {
		os->PutBigEndian(2, 0x000D);
		os->PutBigEndian(2, fp->Size());

		os->PutBigEndian(2, 4);
		os->PutBigEndian(2, fp->Face());

		for (int i= 0; fmap[i].id >= 0; i++)
			if (fp->Fid() == fmap[i].fid) {
				os->PutBigEndian(2, 3);
				os->PutBigEndian(2, fmap[i].id);
				break;
			}
		lastfont= fp;
	}
}

void MacPictPort::PutSize(int s)
{
	if (s >= 0 && lastpsz != s) {
		lastpsz= s;
		if (s == 0)
			s= 1;
		os->PutBigEndian(2, 0x0007);
		PutPoint(s);
	}
}

void MacPictPort::DevClip(const Rectangle &r)
{
	os->PutBigEndian(2, 0x0001);
	os->PutBigEndian(2, 10);
	PutRect(r);
}

void MacPictPort::DevStrokeLine2(int psz, const Rectangle &r, GrLineCap,
					const Point &p1, const Point &p2)
{
	Merge(r);
	PutSize(psz);
	os->PutBigEndian(2, 0x0020);
	PutPoint(p1);
	PutPoint(p2);
}

void MacPictPort::DevShowChar(Font *fdp, const Point &delta, Rune c,
														const Rectangle&)
{
	if (fdp != bfont || delta != gPoint0 || bcnt >= 200)
		FlushAnyText();
		
	if (bcnt == 0)  // first
		bfont= fdp;

	bbuf[bcnt++]= c;
}

void MacPictPort::DevShowTextBatch(const Rectangle &r, const Point &pos)
{
	Merge(r);
	PutFont(bfont);
	if (bcnt > 0) {
		os->PutBigEndian(2, 0x0028);
		PutPoint(pos);
		os->PutBigEndian(1, bcnt);
		os->write(bbuf, bcnt);
		if ((bcnt & 1) == 0)
			os->PutBigEndian(1, 0);     // pad
		bcnt= 0;
	}
}

void MacPictPort::DevStrokeRect2(int psz, const Rectangle &r)
{
	Merge(r);
	PutSize(psz);
	os->PutBigEndian(2, 0x0030);
	PutRect(r);
}

void MacPictPort::DevFillRect(const Rectangle &r)
{
	Merge(r);
	os->PutBigEndian(2, 0x0031);
	PutRect(r);
}

void MacPictPort::DevStrokeOval2(int psz, const Rectangle &r)
{
	Merge(r);
	PutSize(psz);
	os->PutBigEndian(2, 0x0050);
	PutRect(r);
}

void MacPictPort::DevFillOval2(const Rectangle &r)
{
	Merge(r);
	os->PutBigEndian(2, 0x0051);
	PutRect(r);
}

void MacPictPort::DevStrokeRRect2(int psz, const Rectangle &r, const Point &dia)
{
	Merge(r);
	PutSize(psz);
	PutCorner(dia);
	os->PutBigEndian(2, 0x0040);
	PutRect(r);
}

void MacPictPort::DevFillRRect2(const Rectangle &r, const Point &dia)
{
	Merge(r);
	PutCorner(dia);
	os->PutBigEndian(2, 0x0041);
	PutRect(r);
}

void MacPictPort::DevStrokeWedge2(int psz, GrLineCap, const Rectangle &r, int s, int d)
{
	Merge(r);
	PutSize(psz);
	os->PutBigEndian(2, 0x0060);
	PutRect(r);
	os->PutBigEndian(2, s);
	os->PutBigEndian(2, d);
}

void MacPictPort::DevFillWedge2(const Rectangle &r, int s, int d)
{
	Merge(r);
	os->PutBigEndian(2, 0x0061);
	PutRect(r);
	os->PutBigEndian(2, s);
	os->PutBigEndian(2, d);
}

void MacPictPort::DevStrokePolygon2(const Rectangle &r, Point *pts, int n, GrPolyType,
								int psz, GrLineCap)
{
	Merge(r);
	PutSize(psz);
	os->PutBigEndian(2, 0x0070);
	os->PutBigEndian(2, n*4+10);
	PutRect(r);
	for (int i= 0; i < n; i++)
		PutPoint(pts[i]);
}

void MacPictPort::DevFillPolygon2(const Rectangle &r, Point *pts, int n, GrPolyType)
{
	Merge(r);
	os->PutBigEndian(2, 0x0070);
	os->PutBigEndian(2, n*4+10);
	PutRect(r);
	for (int i= 0; i < n; i++)
		PutPoint(pts[i]);
}

int PackBits(byte *s, byte *d, int len)
{
	register byte *src= s, *dst= d, *ss= src;
	int l= len, i, count= 0, rep= 0;
	byte *endp= dst + (len+len/128+1);
		
	while (l-- > 0) {
		byte c= *src++;
		if (count > 0) {
			if (c == src[-2]) {
				if (count > rep+1) {
					if (dst+count > endp)
						break;
					*dst++= count-2;
					for (i= 0; i < count-1; i++)
						*dst++= *ss++;
					rep= 0;
					count= 1;
				}
				rep++;
			} else {
				if (rep >= 1) {
					if (dst+2 > endp)
						break;
					*dst++= 257-count;
					*dst++= *ss;
					ss+= count;
					count= 0;
				}
				rep= 0;
			}
		}
		count++;
		if (count >= 128 || (l == 0 && count > 0)) {
			if (rep > 1) {
				if (dst+2 > endp)
					break;
				*dst++= 257-count;
				*dst++= *ss;
				ss+= count;
			} else {
				if (dst+count+1 > endp)
					break;
				*dst++= count-1;
				for (i= 0; i < count; i++)
					*dst++= *ss++;
			}
			if (l == 0)
				return dst-d;
			count= rep= 0;
		}
	}

	src= s;
	dst= d;
	for (l= len; l > 0;) {
		int n= Math::Min(l, 128);
		*dst++ = n-1;
		for (i= 0; i < n; i++)
			*dst++= *src++;
		l-= n;
	}
	return dst-d;
}

void MacPictPort::DevShowBitmap(const Rectangle &r, Bitmap *bm)
{
	DevBitmap *dbm= bm->GetDevBitmap();
	Merge(r);
	register int y, rowbytes, h= bm->Size().y;

	Rectangle bounds(bm->Size());
	int depth= dbm->Depth();
	int len= (int) bm->MaxPixelValue()+1;

	BitmapInfo bi(bm->Size(), depth, 2);

	rowbytes= ((bounds.extent.x-1)/(16/depth)+1)*2;

	if (rowbytes < 8)
		os->PutBigEndian(2, 0x0090);
	else
		os->PutBigEndian(2, 0x0098);

	if (depth > 1) {
		RGB rgb;
		
		os->PutBigEndian(2, rowbytes+0x8000);
		PutRect(bounds);
		os->PutBigEndian(2, 0);
		os->PutBigEndian(2, 0);
		os->PutBigEndian(4, 0);
		os->PutBigEndian(4, 0x00480000);
		os->PutBigEndian(4, 0x00480000);
		os->PutBigEndian(2, 0);
		os->PutBigEndian(2, depth);
		os->PutBigEndian(2, 1);
		os->PutBigEndian(2, depth);
		os->PutBigEndian(4, 0);
		os->PutBigEndian(4, 0);
		os->PutBigEndian(4, 0);
		
		os->PutBigEndian(4, 0);      // seed
		os->PutBigEndian(2, 0);      // flags
		os->PutBigEndian(2, len-1);  // flags
		for (int i= 0; i < len; i++) {
			bm->PixelToRGB(i, &rgb);
			os->PutBigEndian(2, i);                 // ???
			os->PutBigEndian(2, rgb.red * 257);     // red
			os->PutBigEndian(2, rgb.blue * 257);    // green
			os->PutBigEndian(2, rgb.green * 257);   // blue
		}
		
	} else {
		os->PutBigEndian(2, rowbytes);
		PutRect(bounds);
	}

	Rectangle sz(bm->Size());
	PutRect(sz);
	PutRect(r);

	if (depth > 1)
		os->PutBigEndian(2, 0);         // copy mode
	else
		os->PutBigEndian(2, 1);         // copy mode

	byte *src= new byte[rowbytes];

	if (rowbytes < 8) {
		for (y= 0; y < h; y++) {
			dbm->GetRow(&bi, y, src);
			os->write(src, rowbytes);
		}
	} else {
		int b= (rowbytes > 250) ? 2 : 1;
		byte *dst= new byte[rowbytes + rowbytes/128 + 1];
		for (y= 0; y < h; y++) {
			dbm->GetRow(&bi, y, src);
			int l= PackBits(src, dst, rowbytes);
			os->PutBigEndian(b, l);
			os->write(dst, l);
		}
		delete dst;
	}
	delete src;
}

//---- MacPictPrinter ----------------------------------------------------------

class MacPictPrinter : public Printer {
public:
	MetaDef(MacPictPrinter);
	MacPictPrinter()
		{ }
	const char *GetName()
		{ return "MacPICT"; }
	bool CanSave()
		{ return TRUE; }
	PrintPort *MakePrintPort(const char *name)
		{ return new MacPictPort(name); }
	PrinterSettings *MakeSettings()
		{ return new PrinterSettings(Point(595,842), Point(50), Point(50), gRect0); }
};

NewMetaImpl0(MacPictPrinter, Printer);

